package org.chispa.api.visitors;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

abstract class AbstractSimpleVisitorFactory<R,T,E> implements
		ParamVisitorFactory<R, T,E> {
	
	protected static File SPEC=new File("cipher.spec");
	protected static File IV=new File("iv.spec");
	protected static File ALGO=new File("algo.spec");
	protected static final int KEY_SIZE=128;
	protected Cipher cipher;
	protected SecretKeySpec spec;
	protected BigInteger ivpar;

	
	public SingleParamVisitor<R, T> createVisitor() throws NoSuchAlgorithmException
	{
		String algorithm;
		
		if(!SPEC.exists())
		{
			KeyGenerator kgen;
			try {
				kgen = KeyGenerator.getInstance("AES");
				kgen.init(KEY_SIZE); 
				SecretKey secret = kgen.generateKey();
				spec = new SecretKeySpec(secret.getEncoded(), "AES");
				algorithm="AES/CBC/PKCS5Padding";
				cipher=Cipher.getInstance(algorithm);
				cipher.init(Cipher.ENCRYPT_MODE, secret);
				ivpar=new BigInteger(cipher.getIV());
				writeOut(SPEC,spec);
				writeOut(ALGO,algorithm);
				writeOut(IV,ivpar);
			} catch (NoSuchAlgorithmException e) {
				//Ignore
			} catch (NoSuchPaddingException e) {
				//Ignore
			} catch (InvalidKeyException e) {
				//Ignore
			}
		}
		spec=readIn(SPEC,SecretKeySpec.class);
		ivpar=readIn(IV,BigInteger.class);
		algorithm=readIn(ALGO,String.class);
		try {
			cipher=Cipher.getInstance(algorithm);
			
		} catch (NoSuchAlgorithmException e) {
			// ignore
		} catch (NoSuchPaddingException e) {
			// ignore
		}
		return doCreateVisitor();
	}
	
	protected abstract SingleParamVisitor<R, T> doCreateVisitor() throws NoSuchAlgorithmException;
	
	protected void writeOut(File filename,Object obj)
	{
		FileOutputStream fo = null;
		ObjectOutputStream out = null;
		
			try {
				fo = new FileOutputStream(filename);
				out = new ObjectOutputStream(fo);
				out.writeObject(obj);
				out.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
	}
	
	@SuppressWarnings("unchecked")
	protected<S> S readIn(File filename,Class<S> type)
	{
		S obj = null;
		FileInputStream fis = null;
		ObjectInputStream in = null;
		try {
			fis = new FileInputStream(filename);
			in = new ObjectInputStream(fis);
			obj = (S)in.readObject();
			in.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return obj;

	}

}
